package BLL;

import java.io.IOException;
import java.util.HashMap;
import java.util.Observable;

import javax.bluetooth.BluetoothStateException;

import lejos.pc.comm.NXTCommException;
import lejos.pc.comm.NXTConnectionState;
import lejos.pc.comm.NXTInfo;
import BE.NXTConnectionEvent;
import BE.NXTDataEvent;
import BE.NXTListener;
import DAL.BTCommunicator;

;
/**
 * Model class for implementing MVC pattern. Implemented as Singleton.
 * Implements the {@link NXTListener} interface for registering this class as
 * an event listener to any searches initiated.
 * 
 * @author Danny Johansen, Didzis Petkus, Liviu Berheci.
 * 
 */
public class Model extends Observable implements NXTListener {

	private static Model instance = null;
	private HashMap<String, BTCommunicator> _connectedDevices;
	private SearchTask _searchTask;

	/**
	 * Private constructor to prevent direct instantiation.
	 */
	private Model() {

		_connectedDevices = new HashMap<String, BTCommunicator>();
	}

	/**
	 * Returns a instance of the class if none exists.
	 * 
	 * @return an instance of this class
	 */
	public static Model getInstance() {

		if (instance == null)
			instance = new Model();

		return instance;
	}

	/**
	 * Initiates a new search.
	 * 
	 * @throws NXTCommException on NXT bluetooth errors.
	 * @throws BluetoothStateException on bluetooth stack errors.
	 */
	public void getBluetoothDevices() throws NXTCommException,
			BluetoothStateException {

		_searchTask = new SearchTask(this);
		_searchTask.startup();
	}

	/**
	 * Initiates a new bluetooth connection.
	 * <br>on success: 
	 * <br><pre>	* Notifies observers with the NXTInfo object.
	 * <pre>        * Informs the {@link BTcommunicator}
	 *          mediating this connection to start listening
	 *          for incoming data.</pre> 
	 * 
	 * @param deviceInfo - the NXTInfo object matching the device to connect to.
	 * @return <b>true</b> on successful connection. <b>false</b> otherwise.
	 * @throws IOException on I/O errors.
	 */
	public boolean connectBluetoothDevice(NXTInfo deviceInfo)
			throws IOException {

		if (deviceInfo.connectionState == NXTConnectionState.DISCONNECTED) {
			deviceInfo.connectionState = NXTConnectionState.UNKNOWN;
		}

		BTCommunicator communicator = new BTCommunicator(this);

		boolean con = communicator.connnectDevice(deviceInfo);

		if (con) {
			System.out.println("Connection success!");
			_connectedDevices.put(deviceInfo.deviceAddress, communicator);
//			cHandlers.put(deviceInfo.deviceAddress, new CoordinateHandler(deviceInfo));
			communicator.getDeviceInfo().connectionState = NXTConnectionState.LCP_CONNECTED;
			communicator.ListenForInput();
			System.out.println("Listening for input");
			setChanged();
			notifyObservers(communicator.getDeviceInfo());
		}

		return con;
	}

	/**
	 * Disconnect from the NXT device matching <i>deviceInfo</i>.
	 * 
	 * @param deviceInfo - the NXTInfo bean passed.
	 * @throws IOException on I/O error.
	 */
	public void disconnectBluetoothDevice(NXTInfo deviceInfo)
			throws IOException {

		// for (BTCommunicator device : _activeConnections) {
		// if (device.isConnected(deviceInfo)) {
		// if (device.getDeviceInfo().deviceAddress
		// .equals(deviceInfo.deviceAddress)) {
		// System.out.println("Device found, trying to disconnect...");
		// device.disconnectDevice(deviceInfo);
		// }
		// }
		// }

		if (_connectedDevices.containsKey(deviceInfo.deviceAddress)) {
			_connectedDevices.get(deviceInfo.deviceAddress).disconnectDevice(
					deviceInfo);
			_connectedDevices.remove(deviceInfo.deviceAddress);
			deviceInfo.connectionState = NXTConnectionState.DISCONNECTED;
			setChanged();
			notifyObservers(deviceInfo);
		}
	}

	/**
	 * Stops the running search.
	 * 
	 * @throws BluetoothStateException on bluetooth stack errors.
	 */
	public void stopSearch() throws BluetoothStateException {
		_searchTask.shutdown();
	}

	@Override
	public void NXTDiscovered(NXTConnectionEvent event) {

		NXTInfo[] nxts = event.getNXTs();

		for (NXTInfo device : nxts) {
			if (_connectedDevices.containsKey(device.deviceAddress)) {
				device.connectionState = NXTConnectionState.LCP_CONNECTED;
			} else {
				device.connectionState = NXTConnectionState.DISCONNECTED;
			}
		}

		setChanged();
		notifyObservers(nxts);
	}

	@Override
	public void NXTMapDataReceived(NXTDataEvent event) {
		//CoordinateHandler cHandler = cHandlers.get(event.getDevice().deviceAddress);
		//event.setData(cHandler.handle(event.getData()));
		setChanged();
		notifyObservers(event);
	}

}
